#pragma once
#include <string>
#include <array>
#include <vector>
#include <map>
#include <simple_enum.hpp>
#include <cereal/archives/binary.hpp>
#include <cereal/types/string.hpp>
#include <cereal/types/vector.hpp>
#include <cereal/types/array.hpp>
#include <cereal/types/map.hpp>
#include <cereal/types/memory.hpp>
#include <cereal/archives/json.hpp>

{% macro generate_struct_def(message) -%}
struct {{ message.name }}
{
    {% for field in message.fields %}
    {{ field.type|get_cpp_type_name}} {{ field.name }}{};
    {% endfor %}
};
{% endmacro %}

{% macro generate_enum_def(message) -%}
enum class {{ message.name }}: {{message.underlying_type | get_cpp_type_name}}
{
    {% for item in message.enumItems %}
    {{ item.name}} = {{ item.val }},
    {% endfor %}
};
{% endmacro %}

{% macro generate_class_def(message) -%}
class {{ message.name }}
{
public:
    {% for msg in message.inner_classes %}
    {{ generate_type_def(msg) | indent}}
    {% endfor %}

    {{ message | get_cntr_signature }}
        : {{message | get_initializer_list}}{}
    // 默认构造函数 default constructor
    {{ message.name }}() = default;
    // 拷贝构造函数 copy constructor
    {{ message.name }}(const {{ message.name }}&) = default;
    // 移动构造函数 move constructor
    {{ message.name }}({{ message.name }}&&) = default;
    // 拷贝赋值运算符 copy assignment operator
    {{ message.name }}& operator=(const {{ message.name }}&) = default;
    // 移动赋值运算符 move assignment operator
    {{ message.name }}& operator=({{ message.name }}&&) noexcept = default;
    // 析构函数 destructor
    ~{{ message.name }}() = default; 
    
    {% for field in message.fields %}
    inline {{ field.type|get_return_type}} get_{{ field.name }}() const { return m_{{ field.name }}; }
    inline {{ field.type|get_cpp_type_name}}& get_{{ field.name }}_mutable() { return m_{{ field.name }}; }
    inline void set_{{ field.name }}({{ field.type|get_cpp_type_name}} {{ field.name }}) { m_{{ field.name }} = {{ field | get_move_statement }}; }

    {% endfor %}

    template<class Archive>
    void serialize(Archive& archive) {
        {% for field in message.fields %}
        archive(cereal::make_nvp("{{ field.name }}", m_{{ field.name }}));
        {% endfor %}
    }

private:
    {% for field in message.fields %}
    {{ field.type|get_cpp_type_name}} m_{{ field.name }}{};
    {% endfor %}
};
{% endmacro %}

{% macro generate_type_def(message) -%}
{% if message.__class__.__name__ == 'StructType' %}
{{generate_struct_def(message)}}
{% elif message.__class__.__name__ == 'ClassType' %}
{{generate_class_def(message)}}
{% elif message.__class__.__name__ == 'EnumType' %}
{{generate_enum_def(message)}}
{% endif %}
{% endmacro %}

{% macro generate_combined_name(name1, name2) -%}
{% if name1 != "" %}
{{ [name1, name2]|join('::') -}}
{% else %}
{{name2 -}}
{% endif %}
{% endmacro %}

{% macro generate_enum_free_function(message, pre_name) -%}
{% set full_message_name = generate_combined_name(pre_name, message.name) %}
namespace simple_enum
{
namespace enum_details
{
        template <>
        inline constexpr auto names_v<{{ full_message_name }}> = std::array<std::string_view, {{message.value_count}}>{
            {% for item in message.enumItems %}
            "{{ item.name}}",
            {% endfor %}
        };

        template <>
        inline constexpr auto values_v<{{ full_message_name }}> = std::array<{{message.underlying_type | get_cpp_type_name}}, {{message.value_count}}>{
            {% for item in message.enumItems %}
            {{ item.val}},
            {% endfor %}
        };

        template <>
        inline constexpr auto entries_v<{{ full_message_name }}> = std::array<std::pair<std::string_view, {{message.underlying_type | get_cpp_type_name}}>, {{message.value_count}}>{
            {% for item in message.enumItems %}
            std::pair<std::string_view, {{message.underlying_type | get_cpp_type_name}}>("{{ item.name}}", {{ item.val}}),
            {% endfor %}
        };
} // namespace enum_details
} // namespace simple_enum

namespace cereal {
template <class Archive> inline
std::string save_minimal( Archive const &, {{ full_message_name }} const & t )
{
    std::string str(simple_enum::enum_name<{{ full_message_name }}>(t));
    return str;
}

template <class Archive> inline
void load_minimal( Archive const &, {{ full_message_name }} & t, std::string const & value )
{
    std::string_view sv(value);
    t = simple_enum::enum_cast<{{ full_message_name }}>(sv).value();
}
} // namespace cereal
{% endmacro %}

{% macro generate_struct_free_function(message, pre_name) -%}
{% set full_message_name = generate_combined_name(pre_name, message.name) %}
template<class Archive>
void serialize(Archive& archive,
               {{full_message_name}}& m) {
    {% for field in message.fields %}
    archive(cereal::make_nvp("{{ field.name }}", m.{{ field.name }}));
    {% endfor %}
}
{% endmacro %}

{% macro generate_class_free_function(message, pre_name) -%}
{% set full_message_name = generate_combined_name(pre_name, message.name) %}
{% for msg in message.inner_classes %}
{{ generate_free_function(msg, full_message_name)}}
{% endfor %}
{% endmacro %}

{% macro generate_free_function(message, name) -%}
{% if message.__class__.__name__ == 'EnumType' %}
{{generate_enum_free_function(message, name)}}
{% elif message.__class__.__name__ == 'StructType' %}
{{generate_struct_free_function(message, name)}}
{% elif message.__class__.__name__ == 'ClassType' %}
{{generate_class_free_function(message, name)}}
{% endif %}
{% endmacro %}

{% for message in messages %}
{{generate_type_def(message)}}
{{generate_free_function(message, "")}}
{% endfor %}


